package org.xqh.study.google.guava.collect;

import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import org.junit.Test;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

/**
 * 集合
 * 1、Lists
 * 2、Maps
 * 3、Sets
 * 4、Immutable
 * 5、Multiset
 * 6、Multimap
 * 7、RangeSet
 * 8、RangeMap
 * 9、BiMap
 * 10、Table
 * 11、Range
 */
public class CollectTest {
    /**
     * Lists
     */
    @Test
    public void testLists() {
        // 1 通过工厂直接生产list；泛型约束传入参数的类型；
        List klist = Lists.newArrayList();
        List<Integer> ilist = Lists.newArrayList(1, 2);
        List<String> slist = Lists.newArrayList("1", "2");

        // 2 Array转List
        List<Integer> array_to_list = Lists.newArrayList(new Integer[]{1, 2});

        // 3 Set转List
        List<Integer> set_to_list = Lists.newArrayList(Sets.newHashSet(1, 2));

        // 4、定义固定大小的新集合
        List<Integer> glist = Lists.newArrayListWithCapacity(4);

        // 5 定义预期大小的新集合，生成集合大小公式：5L + arraySize + (arraySize / 10)
        List<Integer> ylist = Lists.newArrayListWithExpectedSize(3);

        // 6 将指定元素加入数组，转型为固定长度list，该list不可变。
        // 多用于将一个标示加入数组的头部，来判断该数组的状态
        List op1list = Lists.asList("a", "b", new String[]{"1","2"}); // [a, b, 1, 2]
        List op2list = Lists.asList("a", new String[]{"1","2"});  // [a, 1, 2]

        // 7 笛卡尔积 展示所有排列组合
        List clist = Lists.cartesianProduct(Lists.newArrayList(1,2,3)
                , Lists.newArrayList(4,5,6)
                , Lists.newArrayList(new Integer[]{7,8,9}));
        // [[1, 4, 7], [1, 4, 8], [1, 4, 9], [1, 5, 7], [1, 5, 8], [1, 5, 9], [1, 6, 7], [1, 6, 8], ...]

        // 8 list分区
        List plist =  Lists.partition(clist, 2);
        // [[[1, 4, 7], [1, 4, 8]], [[1, 4, 9], [1, 5, 7]], [[1, 5, 8], [1, 5, 9]], [[1, 6, 7], [1, 6, 8]],...]]

        // 9 字符串转list,注意返回为char类型的list，*注意：该list不可变
        List imlist = Lists.charactersOf("world"); // [w, o, r, l, d]

        // 10 排序-颠倒，如果传入的list是不可变的，则颠倒以后的也不可变
        List relist1 = Lists.reverse(imlist); // [d, l, r, o, w]

        List relist2 = Lists.reverse(Lists.newArrayList(1, 2, 3, 4, 5));
        relist2.add(6);
        System.out.println(relist2);
    }

    /**
     * Maps
     */
    @Test
    public void testMaps() {
        //1 创建hashMap
        Map<String, String> map = Maps.newHashMap();

        //2 初始化不可变
        Map<String, String> map1 = ImmutableMap.of("k1","v1","k2","v2", "k3", "v3");
        Map<String, String> map3 = ImmutableMap.of("k1","v11","k2","v2", "k4", "v4");

        //3 2个map比较，获取交集、差集、并集
        MapDifference md = Maps.difference(map1, map3);
        Map m = md.entriesOnlyOnLeft(); // 只有左边存在
        Map m1 = md.entriesOnlyOnRight(); // 只有右边存在
        Map m2 = md.entriesDiffering(); // key相同value不同
        Map m3 = md.entriesInCommon(); // 相同

        System.out.println(m);
        System.out.println(m1);
        System.out.println(m2);
        System.out.println(m3);
    }

    /**
     * Sets
     */
    @Test
    public void testSets() {
        HashSet setA = Sets.newHashSet(1, 2, 3, 4, 5);
        HashSet setB = Sets.newHashSet(4, 5, 6, 7, 8);

        // 并集
        Sets.SetView union = Sets.union(setA, setB);
        System.out.println(union);

        // 差集
        Sets.SetView difference = Sets.difference(setA, setB);
        System.out.println(difference);

        // 交集
        Sets.SetView intersection = Sets.intersection(setA, setB);
        System.out.println(intersection);

    }

    /**
     * Immutable 不可变集合
     * 这里以 ImmutableList 为例，另外还有ImmutableMap和ImmutableSet类似
     */
    @Test
    public void testImmutableXXX() {
        //1 直接申明
        ImmutableList<Integer> immutableList = ImmutableList.of(1, 2, 3, 4);

        //2 copy模式
        ImmutableList<Integer> immutableList2 = ImmutableList.copyOf(Lists.newArrayList(1, 2, 3, 4));

        //3 builder模式
        ImmutableList<Integer> immutableList3 = ImmutableList.<Integer>builder().addAll(immutableList).add(5).build();

        //4 排序
        ImmutableList<Integer> immutableList4 = ImmutableList.sortedCopyOf(Lists.newArrayList(1, 6, 3, 5, 2, 4));

        System.out.println(immutableList);
        System.out.println(immutableList2);
        System.out.println(immutableList3);
        System.out.println(immutableList4);

//        immutableList3.set(0, 3);
//        System.out.println(immutableList3);
    }

    //1 Multiset
    //Multiset和Set的区别就是可以保存多个相同的对象。
    // 在JDK中，List和Set有一个基本的区别，就是List可以包含多个相同对象，且是有顺序的，而Set不能有重复，且不保证顺序（有些实现有顺序，例如LinkedHashSet和SortedSet等）。
    // 所以Multiset占据了List和Set之间的一个灰色地带：允许重复，但是不保证顺序。
    @Test
    public void testMultiset() {
        Multiset<String> multiset = HashMultiset.create();
        multiset.add("a");
        multiset.add("a");
        multiset.add("b");
        multiset.add("c");

        System.out.println(multiset);
        System.out.println("元素a出现的次数 " + multiset.count("a"));
        System.out.println("包含重复元素的个数  " + multiset.size());
        System.out.println("不包含重复元素的个数 " + multiset.elementSet().size());


        //SortedMultiset是Multiset 接口的变种，它支持高效地获取指定范围的子集。
        SortedMultiset<String> sortedMultiset = TreeMultiset.create();
        sortedMultiset.add("a");
        sortedMultiset.add("b");
        sortedMultiset.add("c");
        sortedMultiset.add("d");
        sortedMultiset.add("e");

        System.out.println(sortedMultiset);
        SortedMultiset<String> strings = sortedMultiset.subMultiset("a", BoundType.CLOSED, "e", BoundType.OPEN);
        strings.forEach(System.out::print);
    }

    //Guava的Multimap可以很容易地把一个键映射到多个值。
    @Test
    public void testMultimap() {
        ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
        multimap.put("a", 1);
        multimap.put("a", 2);
        multimap.put("a", 3);
        multimap.put("a", 4);
        multimap.put("b", 1);
        System.out.println(multimap.size());

        //  asMap() 提供Map<K,Collection<V>>形式的视图 collections 中
        //  a->{1,2,3,4}
        //  b->{1}
        Collection<Collection<Integer>> collections = multimap.asMap().values();
        System.out.println(collections);

        // values {1,2,3,4,1}
        Collection<Integer> values = multimap.values();
        System.out.println(values);

        Collection<Map.Entry<String, Integer>> entries = multimap.entries();
        entries.forEach(item -> {
            System.out.print("key--->" + item.getKey());
            System.out.println("  value--->" + item.getValue());
        });
    }

    // RangeSet描述了一组不相连的、非空的区间。
    // 当把一个区间添加到可变的RangeSet时，所有相连的区间会被合并，空区间会被忽略。
    @Test
    public void testRangeSet() {
        RangeSet<Integer> rangeSet = TreeRangeSet.create();
        System.out.println(rangeSet);

        rangeSet.add(Range.closed(1, 10)); // {[1,10]}
        System.out.println(rangeSet);

        rangeSet.add(Range.closedOpen(11, 15));//不相连区间:{[1,10], [11,15)}
        System.out.println(rangeSet);

        rangeSet.add(Range.closedOpen(15, 20)); //相连区间; {[1,10], [11,20)}
        System.out.println(rangeSet);

        rangeSet.add(Range.openClosed(0, 0)); //空区间; {[1,10], [11,20)}
        System.out.println(rangeSet);

        rangeSet.remove(Range.open(5, 10)); //分割[1, 10]; {[1,5], [10,10], [11,20)}
        System.out.println(rangeSet);
    }

    // RangeMap描述了”不相交的、非空的区间”到特定值的映射。
    // 和RangeSet不同，RangeMap不会合并相邻的映射，即便相邻的区间映射到相同的值
    @Test
    public void testRangeMap() {
        RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
        System.out.println(rangeMap);

        rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
        System.out.println(rangeMap);

        rangeMap.put(Range.open(3, 6), "bar"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo"}
        System.out.println(rangeMap);

        rangeMap.put(Range.open(10, 20), "foo"); //{[1,3] => "foo", (3,6) => "bar", [6,10] => "foo", (10,20) => "foo"}
        System.out.println(rangeMap);

        rangeMap.remove(Range.closed(5, 11)); //{[1,3] => "foo", (3,5) => "bar", (11,20) => "foo"}
        System.out.println(rangeMap);
    }

    //BiMap<K, V>是特殊的Map：
    //可以用 inverse()反转BiMap<K, V>的键值映射
    //保证值是唯一的，因此values()返回Set而不是普通的Collection
    //在BiMap中，如果你想把键映射到已经存在的值，会抛出IllegalArgumentException异常。如果对特定值，你想要强制替换它的键，请使用BiMap.forcePut(key, value)。
    @Test
    public void testBiMap() {
        BiMap<String, Integer> biMap = HashBiMap.create();
        biMap.put("a", 1);
        System.out.println(biMap.get(1)); //null
        System.out.println(biMap.inverse().get(1)); //a
        System.out.println(biMap); //{a=1}
        System.out.println(biMap.inverse()); //{1=a}
    }

    //Table--矩阵。通常来说，当你想使用多个键做索引的时候,
    //Guava为此提供了新集合类型Table，它有两个支持所有类型的键：”行”和”列”。
    @Test
    public void testTable() {
        Table<String, String, String> table = HashBasedTable.create();
        table.put("a", "b", "ab");
        table.put("a", "c", "ac");

        System.out.println(table.row("a")); //{b=ab, c=ac}
        System.out.println(table.get("a", "b")); //ab
    }

    //Range定义了连续跨度的范围边界，这个连续跨度是一个可以比较的类型(Comparable type)。比如1到100之间的整型数据。
    @Test
    public void testRange() {
        System.out.println("open:"+ Range.open(1, 10)); //open:(1..10)
        System.out.println("closed:"+ Range.closed(1, 10)); //closed:[1..10]
        System.out.println("closedOpen:"+ Range.closedOpen(1, 10)); //closedOpen:[1..10)
        System.out.println("openClosed:"+ Range.openClosed(1, 10)); //openClosed:(1..10]
        System.out.println("greaterThan:"+ Range.greaterThan(10)); //greaterThan:(10..+∞)
        System.out.println("atLeast:"+ Range.atLeast(10)); //atLeast:[10..+∞)
        System.out.println("lessThan:"+ Range.lessThan(10)); //lessThan:(-∞..10)
        System.out.println("atMost:"+ Range.atMost(10)); //atMost:(-∞..10]
        System.out.println("all:"+ Range.all()); //all:(-∞..+∞)
        System.out.println("closed:"+ Range.closed(10, 10)); //closed:[10..10]
        System.out.println("closedOpen:"+ Range.closedOpen(10, 10)); //closedOpen:[10..10)
        //会抛出异常
        //System.out.println("open:"+Range.open(10, 10));

        System.out.println("downTo:"+ Range.downTo(4, BoundType.OPEN)); //downTo:(4..+∞)
        System.out.println("upTo:"+ Range.upTo(4, BoundType.CLOSED)); //upTo:(-∞..4]
        System.out.println("range:"+ Range.range(1, BoundType.CLOSED, 4, BoundType.OPEN)); //range:[1..4)

        System.out.println(Range.closed(1, 3).contains(2)); //true
        System.out.println(Range.closed(1, 3).contains(4)); //false
        System.out.println(Range.lessThan(5).contains(5)); //false
        System.out.println(Range.closed(1, 4).containsAll(Ints.asList(1, 2, 3))); //true
    }
}
